/*  tBookShelf: TextPlugin
    (C) 2007  TvN (Volodymyr Tarasenko), e-mail: tvntsr@yahoo.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <iostream>
#include <sstream>

#include "Utils/DebugStream.hpp"
#include "TextReader.hpp"

#define PAGE_SIZE  600*800/4
#define PAGE_WIDTH 143

using namespace std;

bool TextReader::convertStrToWstr(const std::string& buf, wchar_t** res)
{
//    return L"1234567890\0";

    std::wstringstream str;

    str << buf.c_str();

    size_t size = str.str().size()*sizeof(wchar_t) + sizeof(wchar_t);

    wchar_t *ret = new wchar_t[size];

    memcpy(ret, str.str().c_str(), size);

    ret[size-1]= L'\0';

    *res = ret;

    return true;
}

void TextReader::setPageOrientation(const PageOrientation& to)
{
    if (to == page_orient)
    {
        return;
    }
    
    destroyPages();

    page_orient = to;

    if (!text.empty())
        open(text);
}

void TextReader::changeSpaces(std::string& p, const TextStyle& style)
{
    if (style.ScipSpaces() > 1)
    {
        string templ(style.ScipSpaces(), ' ');
        string::size_type a = 0;
        while( string::npos != (a = p.find(templ, a)))
        {
            p.replace(a, style.ScipSpaces(), 1, ' ');
            
        }
    }

    unsigned int tabs = style.Tabs()<= 1 ? 1 : style.Tabs();

    string tabul(tabs, ' ');
    string::size_type a = 0;
    while( string::npos != (a = p.find("\t", a)))
    {
        p.replace(a, 1, tabul);
    }
}

void TextReader::renderPage(Page* page, bool imitate)
{
    FontManager&  fmt   = fontManager();
    ConfigParser& cfg   = configManager();
    istream&      ftext = streamManager().getStream();

    if (page->isRendered())
    {
        // Already rendered
        return;
    }

    ftext.seekg(page->start_pos, ios::beg);
    if (!ftext.good())
    {
        dout << "TEXT file is bad for output after seekg()!" << endl;
        throw std::runtime_error("TEXT file is bad for output after seekg()");
    }

    if (!page->isValid())
    {
        dout << "Page:" << page->num << " is not valid" << endl; 
        throw std::runtime_error("Page is not valid");
    }
    
    DrawingArea* curr_area = page->getArea();

    dout << "Page ("
         << cfg.getPageWidth(page_orient) << ", "
         << cfg.getPageHeight(page_orient)
         << ")"
         << endl;
    curr_area->setOverallDimension(cfg.getPageWidth(page_orient),
                                   cfg.getPageHeight(page_orient));
    curr_area->putPenToStart();

    curr_area->createBuffer(imitate);
    
    bool quit     = false;
    int  after    = 0;
    int  line_num = 0;

    string old_style_name;
    string btext;

    TextStyle style;
    streampos line_start;

    do
    {
        line_start = ftext.tellg();

        getline(ftext, btext);

        cfg.parseLine(btext, style); // :FIXME: check if it need more lines to work
        
        changeSpaces(btext, style);

        dout << "Line ["
             << btext
             << "] assigned to style '"
             << style.Name() << "'"
             << ", font:" << style.Font()
             << ", font file:" << style.FontFile()
             << endl;

        curr_area->movePen(0, style.Size());// + style.Before();
        
        if ((old_style_name.empty())||
            (old_style_name != style.Name()))
        {
            curr_area->movePen(style.FirstLine(),
                               style.Before() + after);
        }
        else
        {
            curr_area->movePen(0, style.InterLineSpace());
        }

        if (!curr_area->isMoveValid(0, style.Size()))
        {
            if (imitate)
            {
                // put file position back
                ftext.seekg(line_start);
            }
            break;
        }
        if (!imitate)
        {
            cfg.transformLine(btext);
            dout << "Line transformed to ["
                 << btext
                 << "]"
                 << endl;

            wchar_t* wstr;
            convertStrToWstr(btext, &wstr);

            dout << "Y position for this Line (curr_area->coord.end.Y):"
                 << curr_area->pen.Y
                 << endl;

            fmt.renderLine(*curr_area,
                           page_orient,
                           wstr,
                           style);
//             if (curr_area->width * curr_area->pen.Y + curr_area->width < buff_size)
//             {
//                 memset(curr_area->buffer+curr_area->width*curr_area->pen.Y,
//                        0xDF, 
//                        curr_area->width);
//             }
        
            dout << "position for this Line:"
                 << curr_area->pen
                 << endl;
            
            delete[] wstr;
        }
//        curr_area->pen.Y += style.After();
        after = style.After();
        curr_area->pen.X = 0;

        dout << "Files current pos is "
             << ftext.tellg()
             << endl;

        if (ftext.bad())
        {
            dout << "File stream in bad state" << endl;
        }
        if (ftext.fail())
        {
            dout << "Fail state in file stream" << endl;
        }

        if ((ftext.eof())||
            (ftext.tellg() >= page->end_pos))
        {
            quit = true;
        }

        old_style_name = style.Name();
        
    }while(!quit);
}

void TextReader::renderArea(Page* page, 
                            char* buff,
                            const Point& start,
                            const Point& end)
{
    if (!page->isValid())
    {
        dout << "Page:" << page->num 
             << ", orient:" << page->orient
             << " is not valid" << endl
             << "All pages:"
             << getPageCount()
             << endl;
        throw std::runtime_error("Page is not valid");
    }

    if (!page->isRendered())
    {
        dout << "Page "
             << page->num 
             << "is not rendered, render it" << endl;
        renderPage(page);
    }

    cerr << "render done, copy area"
         << endl
         << "Page is " << page->num
         << ", Pages coord:"
         << page->area->coord
         << endl
         << "Area coord:"
         << start << "x" << end
         << ", width:"
         << page->area->width
         << ", orientation: "
         << page->orient
         << endl;

    if ((start.X > page->area->coord.end.X) ||
        (start.Y > page->area->coord.end.Y))
    {
        dout << "Cannot copy page, area is outside page size"
             << endl;
        return;
    }

    if ((end.X == page->area->coord.end.X)    &&
        (end.Y == page->area->coord.end.Y)    &&
        (start.X == page->area->coord.start.X)&&
        (start.Y == page->area->coord.start.Y))
    {
        // Could copy whole block
        copyPageBuffer(page, buff, start, end);
        return;
    }
    
    // Need copy line by line
    dout << "renderArea: Copy line by line" << endl;
    size_t start_page_byte = start.Y*page->area->width + (start.X>>2);
    size_t end_page_byte   = page->area->width
        * (end.Y > page->area->coord.end.Y ? page->area->coord.end.Y : end.Y)
        + (end.X>>2);
    size_t page_lines = (end.Y > page->area->coord.end.Y ? 
                         page->area->coord.end.Y : end.Y) - start.Y;
        

//     cerr << "End:" << end
//          << ", start:" << start
//          << ", (end.X - start.X):" << (end.X - start.X)
//          << ", (7+2*(end.X - start.X)):" << (7+2*(end.X - start.X))
//          << ", window_width =" << ((7+2*(end.X - start.X))>> 3 )
//          << endl;

    size_t window_width      = (7+2*(end.X - start.X))>> 3;
    size_t start_window_byte = start.Y * window_width + (start.X >> 2);
    size_t end_window_byte   = window_width * end.Y + (end.X >> 2);
    size_t window_lines      = end.Y - start.Y;
    

//     cerr << "window_width * end.Y + end.X >> 2:"
//          << window_width * end.Y 
//          << ", " << (end.X >> 2)
//          << ", res:" << (window_width * end.Y + (end.X >> 2))
//          << endl;

    size_t rest = end_window_byte - start_window_byte;

    cerr << "end_window_byte:" << end_window_byte
         <<", start_window_byte:" << start_window_byte
         << ", rest is:"
         << rest
         << endl;

    size_t line_to_copy = window_lines > page_lines ? 
        page_lines : window_lines;

//     cerr << "window_width:"
//          << window_width
//          << ", page width:"
//          << page->area->width
//          << endl
//          << "window_lines:"   << window_lines
//          << ", page_lines:"   << page_lines
//          << ", line_to_copy:" << line_to_copy
//          << endl;

    unsigned char* pbuf = page->area->buffer;

    pbuf += start_page_byte;

    char* to = buff;
    to += start_window_byte;

    rest = 0;
    // copy line by line
    size_t skip = (7+(end.X-page->area->coord.end.X)*2)>>3;
    cerr << "SKIP:" << skip << endl;
    for (size_t copied = 0; copied < line_to_copy; ++ copied)
    {
        memset(to, 0x00, skip);
        to += skip;

        memcpy(to, pbuf, page->area->width);

        pbuf += page->area->width;
        to += window_width - skip;
        continue;


        cerr << "Line:" << copied
             <<", rest=" << rest;
        if (window_width > page->area->width)
        {
            memcpy(to, pbuf, page->area->width);
            to += page->area->width;

            cerr << "-" << page->area->width << "(page width)";
            
            rest -= page->area->width;

            //memset(to, 0xFF, window_width - page->area->width);
            to += window_width - page->area->width;
            rest -= window_width - page->area->width;
            cerr << "-" << window_width - page->area->width 
                 << "(window_width - page->area->width):(" 
                 << window_width << "-" << page->area->width <<")";
        }
        else
        {
            size_t count = window_width < page->area->width ? 
                window_width : page->area->width;

            memcpy(to, pbuf, count);
            to += count;

            cerr << "-" << count << "(count)";

            rest -= count;
        }
        
        cerr<< " = " << rest
             << endl;

        pbuf += page->area->width;
    }

    // if (window_lines > page_lines)
//     {
//         dout << "Need to fill " 
//              << rest 
//              << " bytes"
//              << endl;
//         memset(to, 0xFF, rest);
//     }
}

void TextReader::getPageText(const size_t    num, 
                             PageOrientation orient,
                             const size_t    offset, 
                             std::string&    to,
                             const size_t    chars)
{
    PageCacheManager& pman = cacheManager();

    PageSanity sanity(cacheManager(),
                      cacheManager().getPage(num, orient));
    if (sanity.get() == 0)
    {
        dout << "Cannot create page" << endl;
        throw runtime_error("Cannot create page");
    }

    if (sanity->start_pos + offset > sanity->end_pos)
    {
        dout << "Out of page size" << endl;
        throw runtime_error("Out of page size");
    }

    istream& ftext = streamManager().getStream();
    ftext.seekg(sanity->start_pos + offset, ios::beg);

    string buf;
    to.clear();

    size_t max_chars = sanity->start_pos + offset + chars > sanity->start_pos?
        sanity->start_pos + offset + chars - sanity->start_pos :
        chars;

    while(to.size() < max_chars)
    {
        getline(ftext, buf);
        to += buf;

        if (!ftext.good())
            break;
    }
    if (to.size() > chars)
    {
        to.erase(chars);
    }
}


int TextReader::open(const std::string& file)
{
    if (text != file)
        text = file;

    ConfigParser& cfg      = configManager();
    istream&      ftext    = streamManager().getStream(file);
    size_t        curr_pos = 0;
    size_t        page_num = 0;

    const size_t page_size = cfg.getPageHeight(page_orient);
    const size_t line_size = cfg.getPageWidth(page_orient);

    PageSanity sanity(cacheManager(),
                      cacheManager().createNewPage(page_num, page_orient));

    while((!ftext.eof())&&(!ftext.fail()))
    {
        sanity->start_pos = ftext.tellg();
        sanity->end_pos = (size_t)-1;

        renderPage(sanity.get(), true);

        sanity->end_pos = ftext.tellg();

        sanity.release();

        sanity.reset(cache->createNewPage(++page_num, page_orient));
    }

    ftext.clear();
    sanity->end_pos = ftext.tellg();

    ftext.seekg(0, ios::end);

    int all_pages = sanity->num;

    dout << "Current page:"
         << sanity->num 
         << ", valid:" 
         << sanity->isValid()
         << ", page's start is "
         << sanity->start_pos
         << ", page's end is "
         << sanity->end_pos
         << std::endl;
    
    sanity.release();

    if (cfg.getPreRenderSize())
        preRenderPages(0, cfg.getPreRenderSize());

    return all_pages;
}

void TextReader::preRenderPages(size_t start, size_t count)
{
    dout << "PreRender pages" << std::endl;

    int all_pages = getPageCount();

    PageSanity sanity(cacheManager(), 0);

    for(int pre = 0; pre < count; ++pre)
    {
        if (pre > all_pages)
        {
            break;
        }
        sanity.reset(cacheManager().getCachedPage(start + pre, 
                                                  page_orient));
        if (sanity.get() == 0)
        {
            dout << "Page was not created, falied open"
                 << std::endl;

            throw std::runtime_error("page is not valid");
        }

        renderPage(sanity.get());

        if (!sanity->isRendered())
        {
            dout << "Page is not rendered as it should, error"
                 << endl;
            
            throw std::runtime_error("NOT rendered as it should");
        }
    
        sanity.release();
    }

}

void TextReader::copyPageBuffer(Page* page,
                                char* buff,
                                const Point& start,
                                const Point& end)
{
    dout << "copyPageBuffer: Copy whole block for page" 
         << page->num
         << endl;

    unsigned char* pbuf = page->area->buffer;

    pbuf += (start.X * start.Y) >> 2;

    size_t to_copy = (7 + 2 * (end.X - start.X)*(end.Y - start.Y)) >> 3;

    memcpy(buff, pbuf, to_copy);

    return;

}
